package pt.isel.pdm.ouryamba;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import org.apache.http.util.ByteArrayBuffer;
import winterwell.jtwitter.Twitter.User;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;

public class UserInfoPullService extends Service implements Callback 
{

	private Messenger myMessenger;
	private General app;
	private static final String ID = "UserInfoPullService";
	public static final int GET_USER_INFO = 1, GET_USER_IMAGE = 2;
	public static final String BUNDLE_USER_NAME = "username";
	public static final String BUNDLE_STATUS_NUMBER = "status_number";
	public static final String BUNDLE_SUBSCRIBERS_NUMBER = "subscribers_number";
	public static final String BUNDLE_SUBSCRIPTIONS_NUMBER = "subscriptions_number";
	public static final String BUNDLE_IMAGE = "image";
	private HandlerThread handlerThread = new HandlerThread("handlerthread_worker");
	
//Service methods
	
	/**
	 * When this is called, there is a new client, so returns the binder object to him.
	 * 
	 * Returns a binder from the messenger of this service.
	 * This method will be called only once. Future clients will receive the same binder returned
	 * on the first time this method was executed. This behavior is delegate by the android, so
	 * every client will have the same binder object.
	 * 
	 *  @return The binder object which the client can use to interact with the service.
	 * */
	@Override
	public IBinder onBind(Intent arg0)
	{
		app.log(ID + ".onBind()");
		return myMessenger.getBinder();
	}

	/**
	 * Starts the handler thread and creates a messenger that is related to the looper of that handler thread.
	 * 
	 * The handler thread is needed to perform network calls in a separate thread. The messenger
	 * is assigned to the looper of the hander thread through a handler that is created.
	 * That handler belongs to the looper and the body of that handler is implemented in the method
	 * {@code handleMessage(Message)} of this class. Thats why this class implements the Callback 
	 * interface.
	 * 
	 * @see #handleMessage(Message)
	 * */
	@Override
	public void onCreate() 
	{
		super.onCreate();
		handlerThread.start();
		app = (General) getApplication();
		myMessenger = new Messenger(new Handler(handlerThread.getLooper(), this));
		app.log(ID + ".onCreate()");
	}

	/**
	 * Interrupts the handler thread and clean up the variables, referencing them to null.
	 * 
	 * @see #cleanUp()
	 * */
	@Override
	public void onDestroy() 
	{
		handlerThread.interrupt();
		cleanUp();
		super.onDestroy();
	}
	
	
//Callback methods	
	
	/**
	 * Delegates the behavior for each type of request received from the service.
	 * 
	 * If the request is for user info, the information is requested from the server and introduced
	 * inside the message bundle.
	 * If the request is for user image, the image is requested from the server and introduced inside
	 * the message bundle.
	 * */
	@Override
	public boolean handleMessage(Message msg) 
	{
		try
		{
			String username = app.getUsername();
			if(username.isEmpty())
			{
				app.log(ID + ".handleMessage(): The user name is empty.");
				return false;
			}
			Message response = Message.obtain();
			response.what = msg.what;
			Bundle bundle = response.getData();
			if(!app.hasNetworkConnection())
			{
				if(msg.what == GET_USER_INFO)
				{
					app.showToast(R.string.noConnectivity);
					fillBundle(bundle, General.EMPTY_STRING, General.EMPTY_STRING, General.EMPTY_STRING, General.EMPTY_STRING);
				}
				msg.replyTo.send(response);
				return false;
			}
			User user = app.getTwitter().getUser(username);
			switch (msg.what)
            {
                case GET_USER_INFO:
                	fillBundle(bundle, user.getName(), String.valueOf(user.getStatusesCount()), String.valueOf(user.getFollowersCount()), String.valueOf(user.getFavoritesCount()));
                	msg.replyTo.send(response);
                    return true;
                case GET_USER_IMAGE:
                    bundle.putByteArray(BUNDLE_IMAGE, requestImageFromUri(user.getProfileImageUrl()));
                    msg.replyTo.send(response);
                    return true;
                default:
                    return false;
            }
		}
		catch (Exception e)
		{
			app.log("UserInfoPullService.handleMessage(): Exception thrown.");
			app.logException(e);
		}
		return false;
	}

	
//Own methods
	
	/**
	 * Put the parameters inside the bundle also received by parameter.
	 * 
	 * @param bundle Is the bundle to fill in.
	 * @param username Is the username to put into the bundle.
	 * @param statusCount Is the count of status of this user to put into the bundle.
	 * @param followers Is the count of followers of this user to put into the bundle.
	 * @param subcriptions Is the count of subscriptions of this user to put into the bundle.
	 * */
	private void fillBundle(Bundle bundle, String username, String statusCount, String followers, String subscriptions)
	{
		bundle.putString(BUNDLE_USER_NAME, username);
        bundle.putString(BUNDLE_STATUS_NUMBER, statusCount);
        bundle.putString(BUNDLE_SUBSCRIBERS_NUMBER, followers);
        bundle.putString(BUNDLE_SUBSCRIPTIONS_NUMBER, subscriptions);
	}
	
	/**
	 * Fetch the image from the web and convert it to a byte array.
	 * 
	 * @param uri Is the URI of the image.
	 * @return The byte array corresponding to the image. Can also return null if some error occurred.
	 * */
	private byte[] requestImageFromUri(URI uri)
	{
		try 
		{
			URL url = uri.toURL();
			URLConnection urlconn = url.openConnection();
			InputStream is = urlconn.getInputStream();
			BufferedInputStream bis = new BufferedInputStream(is);
			ByteArrayBuffer baf = new ByteArrayBuffer(100);
			int current = 0;
			while ((current = bis.read()) != -1) 
			{
                baf.append((byte) current);
			}
			return baf.toByteArray();
		}
		catch (Exception e) 
		{
			app.log(ID + ".requestImageFromUri(URI): Exception thrown.");
			app.logException(e);
		}
		return null;	
	}
	
	
	/**
	 * Reference most variables to null to help the GC job when its called. 
	 * */
	private void cleanUp()
	{
		myMessenger = null;
		app = null;
		handlerThread = null;
	}
}
